package org.crud4j.validator;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Validator {

	@SuppressWarnings("unchecked")
	private Map<Class, CompositeBeanValidator> classValidators = new HashMap<Class, CompositeBeanValidator>();

	@SuppressWarnings("unchecked")
	private Map<Class, Class> valueValidators = new HashMap<Class, Class>();

	public Validator() {
		super();
		this.valueValidators.put(Nullable.class, NullableValidator.class);
	}

	/**
	 * Validates a bean
	 * 
	 * @param bean
	 *            the bean
	 * @throws BeanValidatorException
	 *             if the bean is invalid according to its
	 *             {@link ValueValidator validators} rules
	 */
	@SuppressWarnings("unchecked")
	public <T> void validate(T bean) throws BeanValidatorException {
		if (!classValidators.containsKey(bean.getClass())) {
			try {
				classValidators.put(bean.getClass(), parse(bean.getClass()));
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		classValidators.get(bean.getClass()).validate(bean);
	}

	/**
	 * Parses the given {@link Class} to build a {@link CompositeBeanValidator}
	 * instance
	 * 
	 * @param type
	 *            the given class
	 * @return the {@link CompositeBeanValidator}
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	private synchronized <T> CompositeBeanValidator<T> parse(Class<T> type)
			throws InstantiationException, IllegalAccessException {

		CompositeBeanValidator<T> classValidator = new CompositeBeanValidator<T>();

		for (Method method : type.getMethods()) {

			// process only methods without parameters
			if (method.getParameterTypes().length > 0) {
				continue;
			}

			// Create a method validator for each registered validation
			// annotation
			Annotation[] annotations = method.getAnnotations();
			for (Annotation annotation : annotations) {
				if (valueValidators.containsKey(annotation.annotationType())) {
					MethodValidator<T> methodValidator = new MethodValidator<T>(
							method, createValidator(annotation));
					classValidator.addBeanValidator(methodValidator);
				}
			}
		}

		for (Field field : type.getDeclaredFields()) {

			// Create a method validator for each registered validation
			Annotation[] annotations = field.getAnnotations();
			for (Annotation annotation : annotations) {
				if (valueValidators.containsKey(annotation.annotationType())) {
					FieldValidator<T> methodValidator = new FieldValidator<T>(
							field, createValidator(annotation));
					classValidator.addBeanValidator(methodValidator);
				}
			}
		}

		return classValidator;
	}

	private ValueValidator createValidator(Annotation annotation)
			throws InstantiationException, IllegalAccessException {
		ValueValidator validator = (ValueValidator) valueValidators.get(
				annotation.annotationType()).newInstance();
		if (!Nullable.class.isAssignableFrom(annotation.getClass())) {
			validator = new NullAwareBeanValidatorWrapper(validator);
		}
		validator.init(annotation);
		return validator;
	}

	@SuppressWarnings("unchecked")
	public void addValueValidator(Class type) {
		try {
			ValueValidator validator = (ValueValidator) type.newInstance();
			this.valueValidators.put(validator.getAnnotationClass(), type);
		} catch (Exception e) {
			throw new RuntimeException("unable to register validator "
					+ type.getName() + ". " + e.getMessage());
		}
	}

}
